/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  bool usingIndex = false;
  BTreeIndex index;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  bool keep_key = true;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  // try to open index file
  if ((rc = index.open(table + ".idx", 'r')) == 0)
      usingIndex = true;

  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  // set ints to maximum unsigned value in order to detect if they will be used
  int max_start = INT_MAX;
  int max_finish = INT_MAX;

  int eq_key = INT_MAX;
  vector<int> ne_keys;
  for (int i = 0; i < cond.size(); i++)
  {
	  int val = atoi(cond[i].value);
      if (cond[i].attr == 1)
      {
        switch (cond[i].comp)
        {
            case SelCond::GT:
                if (max_start == INT_MAX || val > max_start)
                    max_start = val+1;
                break;
            case SelCond::LT:
                if (max_finish == INT_MAX || val < max_finish)
                    max_finish = val-1;
                break;
            case SelCond::GE:
                if (max_start == INT_MAX || val > max_start)
                    max_start = val;
                break;
            case SelCond::LE:
                if (max_finish == INT_MAX || val < max_finish)
                    max_finish = val;
                break;
            case SelCond::EQ:
                // two eqs, both can't be true
                if (eq_key != INT_MAX)
                    goto exit_select;
                eq_key = val;
                break;
            case SelCond::NE:
                ne_keys.push_back(val);
                break;
        }
      }
  }
  if (max_start == INT_MAX && max_finish != INT_MAX)
      max_start = 0;
  if (max_start > max_finish)
      goto exit_select;


  for (int i = 0; i < ne_keys.size(); i++)
  {
    if (ne_keys.at(i) == eq_key)
        keep_key = false;
  }

  if (eq_key != INT_MAX &&
          keep_key &&
          (max_start == INT_MAX || (max_start != INT_MAX && eq_key >= max_start)) &&
          (max_finish == INT_MAX || (max_finish != INT_MAX && eq_key <= max_finish)))
  {
      // locate equal stuff
      IndexCursor cursor;
      // may need more detailed error reporting
      if ((rc = index.locate(eq_key, cursor)) < 0) return rc;
      RecordId eq_rid;
      int key;
      if ((rc = index.readForward(cursor, key, eq_rid)) < 0) return rc;
      string value;
      if ((rc = rf.read(eq_rid, key, value)) < 0) return rc;
      if (key == eq_key)
      {
        count = 1;
        switch (attr) {
        case 1:  // SELECT key
         fprintf(stdout, "%d\n", key);
         break;
        case 2:  // SELECT value
         fprintf(stdout, "%s\n", value.c_str());
         break;
        case 3:  // SELECT *
         fprintf(stdout, "%d '%s'\n", key, value.c_str());
         break;
        }
      }

  }
  else if (max_start != INT_MAX && eq_key == INT_MAX)
  {
      // locate equal stuff
      IndexCursor cursor;
      // may need more detailed error reporting
      if ((rc = index.locate(max_start, cursor)) < 0) return rc;
      RecordId eq_rid;
      int key = max_start; 
      RC readRC = 0;
      do
      {
        if ((readRC = index.readForward(cursor, key, eq_rid)) < 0 && readRC != RC_END_OF_TREE) return readRC;
        if (key >= max_finish)
            break;

        string value;
        if ((rc = rf.read(eq_rid, key, value)) < 0) return rc;
     // print the tuple 
        bool keep = true;
        for (int i = 0; i < ne_keys.size(); i++)
        {
            if (ne_keys.at(i) == key)
                keep = false;
        }
         if (keep)
         { 
            count++;
          switch (attr) {
           case 1:  // SELECT key
            fprintf(stdout, "%d\n", key);
           break;
           case 2:  // SELECT value
            fprintf(stdout, "%s\n", value.c_str());
            break;
           case 3:  // SELECT *
            fprintf(stdout, "%d '%s'\n", key, value.c_str());
            break;
          }
         }
      } while (readRC != RC_END_OF_TREE);
  }
  else
  {
      // ******************************************************
      while (rid < rf.endRid()) {
        // read the tuple
        if ((rc = rf.read(rid, key, value)) < 0) {
          fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
          goto exit_select;
        }

        // check the conditions on the tuple
        for (unsigned i = 0; i < cond.size(); i++) {
          // compute the difference between the tuple value and the condition value
          switch (cond[i].attr) {
          case 1:
        diff = key - atoi(cond[i].value);
        break;
          case 2:
        diff = strcmp(value.c_str(), cond[i].value);
        break;
          }

          // skip the tuple if any condition is not met
          switch (cond[i].comp) {
          case SelCond::EQ:
        if (diff != 0) goto next_tuple;
        break;
          case SelCond::NE:
        if (diff == 0) goto next_tuple;
        break;
          case SelCond::GT:
        if (diff <= 0) goto next_tuple;
        break;
          case SelCond::LT:
        if (diff >= 0) goto next_tuple;
        break;
          case SelCond::GE:
        if (diff < 0) goto next_tuple;
        break;
          case SelCond::LE:
        if (diff > 0) goto next_tuple;
        break;
          }
        }

        // the condition is met for the tuple. 
        // increase matching tuple counter
        count++;

        // print the tuple 
        switch (attr) {
        case 1:  // SELECT key
          fprintf(stdout, "%d\n", key);
          break;
        case 2:  // SELECT value
          fprintf(stdout, "%s\n", value.c_str());
          break;
        case 3:  // SELECT *
          fprintf(stdout, "%d '%s'\n", key, value.c_str());
          break;
        }

        // move to the next tuple
        next_tuple:
        ++rid;
      }
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
    RC rc;
    BTreeIndex treeIndex;
    if (index &&
       (rc = treeIndex.open(table + ".idx", 'w')) < 0) return rc;

    // open input file
    ifstream in(loadfile.c_str());
    if (!in)
        return RC_FILE_OPEN_FAILED;

    // open output record
    RecordFile newRf;
    RC open_result = newRf.open(table + ".tbl", 'w');
    if (open_result != 0)
    {
        in.close();
        newRf.close();
        if (index &&
           (rc = treeIndex.close()) < 0) return rc;
        return open_result;
    }

    // read input file and insert records into output record
    string line;
    while (getline(in, line))
    {
        int key;
        string value;

        RC parse_result = parseLoadLine(line, key, value);
        if (parse_result != 0)
        {
            in.close();
            newRf.close();
            if (index &&
               (rc = treeIndex.close()) < 0) return rc;
            return parse_result;
        }
        
        RecordId nextRid = newRf.endRid();
        RC append_result = newRf.append(key, value, nextRid);
        if (index &&
            (rc = treeIndex.insert(key, nextRid)) < 0) return rc;
        if (append_result != 0)
        {
            in.close();
            newRf.close();
            if (index &&
               (rc = treeIndex.close()) < 0) return rc;
            return append_result;
        }

    }
    /*
    if (index)
    {
        cout << "FINAL TREE: " << endl << endl << endl << endl << endl;
        cout << "PRINTING TREE" << endl;
        printBTree(treeIndex);
    }
    */
    if (index && 
        (rc = treeIndex.close()) < 0) return rc;
    in.close();
    newRf.close();

    return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
